<?php
namespace Martdb\Utils;

use Martdb\Exceptions\TIllegalArgumentException;
use Martdb\Libs\ThinUtil;

/**
 * List类型
 *
 * @author Chunlin Jing
 */
class TList
{
    // size, isEmpty, contains, indexOf, [lastIndexOf],
    // toArray, get, set, add/insert, remove,
    // addAll/insertAll, [removeAll], clear, ...
    
    // The array buffer into which the elements of the ArrayList are stored.
    // The capacity of the ArrayList is the length of this array buffer.
    private $elementData = array();
    
    // The size of the TList (the number of elements it contains).
    private $size = 0;
    
    public function __construct($c = null) {
        if (is_null($c)) {
            return;
        }
        
        $element = null;
        if (is_array($c)) {
            $element = $c;
        } else if ($c instanceof TList) {
            $element = $c->elementData;
        } else {
            throw new TIllegalArgumentException("Value must be array() or TList");
        }
        
        // Avoid indexes not numbers
        foreach ($element as $key => $value) {
            $this->elementData[$this->size++] = $value;
        }
    }
    
    // Returns the number of elements in this list.
    public function size() {
        return $this->size;
    }
    
    // Returns <tt>true</tt> if this list contains no elements.
    public function isEmpty() {
        return $this->size == 0;
    }
    
    // Returns <tt>true</tt> if this collection contains the specified element.
    public function contains($o) {
        return in_array($o, $this->elementData, true);
    }
    
    // Returns the index of the first occurrence of the specified element
    // in this list, or -1 if this list does not contain the element.
    public function indexOf($o) {
        $index = array_search($o, $this->elementData, true);
        return is_bool($index) ? -1 : $index;
    }
    
    // Returns the element at the specified position in this list.
    public function get($index) {
        $this->rangeCheck($index);
        
        return $this->elementData[$index];
    }
    
    // Replaces the element at the specified position in this list with
    // the specified element.
    public function set($index, $element) {
        $this->rangeCheck($index);
        
        $oldValue = $this->elementData[$index];
        $this->elementData[$index] = $element;
        return $oldValue;
    }
    
    // Appends the specified element to the end of this list.
    public function add($element) {
        // PHP is a dynamic array, so there is no need to ensure capacity
        $this->elementData[$this->size++] = $element;
    }
    
    // Inserts the specified element at the specified position in this list.
    public function insert($index, $element) {
        $this->rangeCheckForAdd($index);
        
        if ($index == 0) {
            $newElementData = array($element);
            ThinUtil::arraycopy($this->elementData, 0, $newElementData, 1, $this->size);
            $this->elementData = $newElementData;
            $this->size++;
        } else if ($index == $this->size) {
            $this->elementData[$this->size++] = $element;
        } else {
            $newElementData = array();
            ThinUtil::arraycopy($this->elementData, 0, $newElementData, 0, $index);
            $newElementData[$index] = $element;
            ThinUtil::arraycopy($this->elementData, $index, $newElementData, $index + 1, $this->size - $index);
            $this->elementData = $newElementData;
            $this->size++;
        }
    }
    
    // Removes the element at the specified position in this list.
    // Shifts any subsequent elements to the left (subtracts one from their indices).
    public function remove($index) {
        $this->rangeCheck($index);
        
        $oldValue = $this->elementData[$index];
        
        $numMoved = $this->size - $index - 1;
        if ($numMoved > 0) {
            ThinUtil::arraycopy($this->elementData, $index + 1, $this->elementData, $index, $numMoved);
        }
        $this->elementData[--$this->size] = null; // clear to let GC do its work
        
        return $oldValue;
    }
    
    // Appends all of the elements in the specified collection to the end of this list.
    public function addAll($c) {
        $element = null;
        if (is_array($c)) {
            $element = $c;
        } else if ($c instanceof TList) {
            $element = $c->elementData;
        } else {
            throw new TIllegalArgumentException("Parameter must be array() or List");
        }
        foreach ($element as $key => $value) {
            $this->elementData[$this->size++] = $value;
        }
    }
    
    // Inserts all of the elements in the specified collection into this
    // list, starting at the specified position.
    public function insertAll($index, $c) {
        $this->rangeCheckForAdd($index);
        
        $element = null;
        $size = 0;
        if (is_array($c)) {
            $element = $c;
            $size = count($c);
        } else if ($c instanceof TList) {
            $element = $c->elementData;
            $size = $c->size;
        } else {
            throw new TIllegalArgumentException("Parameter must be array() or List");
        }
        
        if ($size == 0) {
            return;
        }
        
        if ($index == 0) {
            $newElementData = array();
            $i = 0;
            foreach ($element as $key => $value) {
                $newElementData[$i++] = $value;
            }
            ThinUtil::arraycopy($this->elementData, 0, $newElementData, $i, $this->size);
            $this->elementData = $newElementData;
            $this->size += $i;
        } else if ($index == $this->size) {
            foreach ($element as $key => $value) {
                $this->elementData[$this->size++] = $value;
            }
        } else {
            $newElementData = array();
            ThinUtil::arraycopy($this->elementData, 0, $newElementData, 0, $index);
            
            $i = $index;
            foreach ($element as $key => $value) {
                $newElementData[$i++] = $value;
            }
            
            ThinUtil::arraycopy($this->elementData, $index, $newElementData, $index + $i, $this->size - $index);
            $this->elementData = $newElementData;
            $this->size += ($i - $index);
        }
    }
    
    // Removes all of the elements from this list.
    // The list will be empty after this call returns.
    public function clear() {
        $this->elementData = array();
        $this->size = 0;
    }
    
    // Returns an array containing all of the elements in this list
    // in proper sequence (from first to last element).
    public function toArray() {
        $a = array();
        ThinUtil::arraycopy($this->elementData, 0, $a, 0, $this->size);
        return $a;
    }
    
    private function rangeCheck($index) {
        if ($index >= $this->size) {
            throw new TIndexOutOfBoundsException("Index: ".$index.", Size: ".$this->size);
        }
    }
    
    // A version of rangeCheck used by add(i, e) and addAll(i, e).
    private function rangeCheckForAdd($index) {
        if ($index > $this->size || $index < 0) {
            throw new TIndexOutOfBoundsException("Index: ".$index.", Size: ".$this->size);
        }
    }
    
    // Override
    public function __toString() {
        $str = "[";
        if ($this->size == 0) {
            return $str."]";
        }
        $e = $this->elementData[0];
        $e = is_bool($e) ? ($e ? "true" : "false") : $e;
        $str .= $e;
        for ($i = 1, $j = $this->size; $i < $j; $i++) {
            $e = $this->elementData[$i];
            $e = is_bool($e) ? ($e ? "true" : "false") : $e;
            $str .= ", ".$e;
        }
        return $str."]";
    }
}

